/* Janus Chat - Version 1.0
 * Authors: Matthew Perry
 *          Michael Freid
 *          Marc Latou
 *          Richard Lee
 *
 * The Janus Chat Client is a highly secured
 * peer-to-peer chat client designed to facilitate
 * the transfer of sensitive data or as a communication
 * medium for the extra paranoid
 *
 * This file contains the implementation for a Unix Socket Class
 * wrapper. This will facilitate the production of janus'
 * functions. */

#include "socket.h"
#include "exceptions.h"
#include "unix_exceptions.h" //UNIX ONLY!!

/* This socket class uses the design pattern Facade.
 * A facade is an interface which collects functions from
 * an existing library and groups them in an easy to manage
 * fashion. This class groups the old C UNIX socket library
 * functions into a simple class facade for easier use. */

/* The Socket constructor:
 * This constructor creates a file descriptor
 * for our underlying socket */
Socket::Socket()
{
    //Create a new socket using TCP and the Internet
    myFD = socket( AF_INET, SOCK_STREAM, 0 );
    //Since something can go wrong here, throw an exception
    if( myFD < 0 )
        throw new SocketException( "Could not create socket" );

    //Set the peer family info to internet
    peerInfo.sin_family = AF_INET;
}

/* The Socket alternate constructor:
 * Copy Constructor */
Socket::Socket( const Socket& sock )
{
    //Initialize member variables with given info
    myFD = sock.myFD;
    peerInfo = sock.peerInfo;
    peerAddr = sock.peerAddr;
    peerPort = sock.peerPort;
}

/* The Socket alternate constructor:
 * This constructor takes an existing file
 * descriptor and peer info and forms a socket
 * class. */
Socket::Socket( int FD, struct sockaddr_in peer )
{
    //Initialize member variables with given info
    myFD = FD;
    peerInfo = peer;
    peerAddr = inet_ntoa( peer.sin_addr );
    peerPort = ntohs( peer.sin_port );
}

/* The Socket alternate construct:
 * This constructor takes an existing file
 * descriptor, peer info, and a timeout 
 * value and forms a socket class. */
Socket::Socket( int FD, struct sockaddr_in peer, struct timeval timeout )
{
    //Initialize member variables with given info
    myFD = FD;
    peerInfo = peer;
    peerAddr = inet_ntoa( peer.sin_addr );
    peerPort = ntohs( peer.sin_port );

    setsockopt(myFD, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); // sets the timeout
}

/* The connectTo function:
 * This function takes an IP and a Port and
 * connects this socket to that host */
void Socket::connectTo( const string& ip, int port )
{
    //Set some member variables for later use
    peerAddr = ip;
    peerPort = port;

    //Set up a hostname lookup struct
    struct hostent* hp;
    //Takes a hostname and looks up an IP address
    hp = gethostbyname( ip.c_str() );
    //Throw an exception if something went wrong
    if( hp == NULL ) 
        throw new LookupException( "Could not find host" );
    //Copy the found IP address to the peerInfo struct
    bcopy( (char *)hp->h_addr, (char *)&peerInfo.sin_addr, hp->h_length );

    //Convert the port int to network endian format
    peerInfo.sin_port = htons( port );

    //Connect the socket using the aquired info
    int retval = connect( myFD, (struct sockaddr *)&peerInfo, sizeof( peerInfo ) );
    //Throw an exceptin if something went wrong
    if( retval < 0 )
        throw new SocketException( "Could not connect" );
}

/* The closeSock function:
 * This function just closes the file
 * descriptor associated with this socket */
void Socket::closeSock()
{
    //Call the UNIX standard close
    int retval = close( myFD );
    //Throw an exception if there was a problem
    if( retval < 0 )
        throw new SocketException( "Could not close socket" );
}

/* The sendMsg function:
 * This function takes in a message
 * and writes it to the socket */
void Socket::sendMsg( Message& msg ) const
{
    //Send the message
    int retval = send( myFD, msg.toChar(), Message::MAX_SIZE, 0 );
    //Throw an exception if the entire message wasnt sent
    if( retval < Message::MAX_SIZE )
        throw new SocketException( "Could not write to socket" );
}

/* The recvMsg function:
 * This function reads a message from
 * the socket as a char string and decodes
 * it and returns a message */
Message Socket::recvMsg() const
{
    //Create buffer for raw data
    char* buffer = new char[ Message::MAX_SIZE ];

    //Read bytes from the socket
    int retval = recv( myFD, buffer, Message::MAX_SIZE, 0 );
    //Throw an exception if there was a problem
    if( retval < Message::MAX_SIZE )
        throw new SocketException( "Could not read from socket" );

    //Return the constructed message
    return Message( buffer, retval );
}
